/* 
 * File:   x_except.hpp
 * Author: marcos assis
 *
 * Created on 11 de Junho de 2010, 23:10
 */

#ifndef _X_EXCEPT_HPP
#define	_X_EXCEPT_HPP

#include <stdexcept>
#include <cstring>
#include <cerrno>


template<class T_data, class T_base_excep=std::runtime_error>
struct data_exception : public T_base_excep
{
  typedef T_data        data_t;
  typedef T_base_excep  base_excep_t;

  explicit data_exception(const std::string& what_arg=std::string(),
                          const data_t& data=data_t(), int ern=0)
  : base_excep_t(what_arg), data_(data), ern_(ern)
  {
    try
    {
      strncpy(msg,base_excep_t::what(),MSG_SIZE);
      if(this->error_number())
      {
        const char* se = strerror(this->error_number());
        if(se)
          strncat(msg, se, MSG_SIZE-strlen(msg));
      }
    }
    catch(...)
    {
      strncpy(msg,base_excep_t::what(),MSG_SIZE);
    }
  }
  
  virtual ~data_exception() throw() {}

  virtual const char* what() const throw()
  {
    return msg;
  }

  virtual int error_number() const throw()
  {
    return ern_;
  }

  data_t data_;
  const static size_t MSG_SIZE = 1024;

protected:
  int ern_;
  char msg[MSG_SIZE];
};

struct errno_exception: public data_exception<int>
{
  typedef data_exception<int>   base_excep_t;

  explicit errno_exception(int ern, const std::string& pre_str=std::string())
  : base_excep_t(pre_str,ern,ern)
  {}

  virtual ~errno_exception() throw() {}
};


#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

  #include <netdb.h>

  struct gai_exception: public data_exception<int>
  {
    typedef data_exception<int>   base_excep_t;

    explicit gai_exception(int gai_err, const std::string& pre_str=std::string())
    : base_excep_t(pre_str,gai_err,gai_err)
    {}

    virtual const char* what() const throw()
    {
      try
      {
        std::string w = base_excep_t::base_excep_t::what();
        if(error_number())
        {
          const char* se = gai_strerror(error_number());
          if(se)
            w += se;
        }
        return w.c_str();
      }
      catch(...)
      {
        return base_excep_t::base_excep_t::what();
      }
    }

    virtual ~gai_exception() throw() {}
  };

#endif



// to allow semicolon after the macro
#define X_SAFE_MACRO(macro) do{ macro }while(0)


#define X_THROW_IF_imp(cond, except) \
  if((cond)) throw (except);

#define X_THROW_IF(cond, except) \
  X_SAFE_MACRO(   X_THROW_IF_imp(cond, except)   )


// to use instead of assert
#if NDEBUG
  #define X_DEBUG_THROW_IF(cond, except)
#else
  #define X_DEBUG_THROW_IF(cond, except)  X_THROW_IF(cond, except)
#endif


// std exceptions
#define X_THROW_RUNTIME_IF(cond, whatarg) \
  X_THROW_IF(cond,std::runtime_error(whatarg))

#define X_DEBUG_THROW_RUNTIME_IF(cond, whatarg) \
  X_DEBUG_THROW_IF(cond,std::runtime_error(whatarg))

#define X_THROW_LOGIC_IF(cond, whatarg) \
  X_THROW_IF(cond,std::logic_error(whatarg))

#define X_DEBUG_THROW_LOGIC_IF(cond, whatarg) \
  X_DEBUG_THROW_IF(cond,std::logic_error(whatarg))

#define X_THROW_INV_ARG_IF(cond, whatarg) \
  X_THROW_IF(cond,std::invalid_argument(whatarg))

#define X_DEBUG_THROW_INV_ARG_IF(cond, whatarg) \
  X_DEBUG_THROW_IF(cond,std::invalid_argument(whatarg))

#define X_THROW_RANGE_IF(cond, whatarg) \
  X_THROW_IF(cond,std::range_error(whatarg))

#define X_DEBUG_THROW_RANGE_IF(cond, whatarg) \
  X_DEBUG_THROW_IF(cond,std::range_error(whatarg))


// throws data_exception with some int
#define X_THROW_COUNT_IF(cond, whatarg, count, ern) \
  X_THROW_IF(cond,data_exception<int>(whatarg, count, ern))

#define X_DEBUG_THROW_COUNT_IF(cond, whatarg) \
  X_DEBUG_THROW_IF(cond,data_exception<int>(whatarg, count, ern))


// throws errno_exception with ern code using strerror()
#define X_THROW_ERROR_IF(cond, ern, pre_str) \
  X_THROW_IF((cond), errno_exception(ern,pre_str))

#define X_DEBUG_THROW_ERROR_IF(cond, ern, pre_str) \
  X_DEBUG_THROW_IF((cond), errno_exception(ern,pre_str))


#define X_THROW_ERRNO_IF(cond, pre_str) \
  X_THROW_ERROR_IF((cond), errno, (pre_str))

#define X_DEBUG_THROW_ERRNO_IF(cond, pre_str) \
  X_DEBUG_THROW_ERROR_IF((cond), errno, (pre_str))


#define X_THROW_ERRNO_IF_NEG(return_value, pre_str) \
  X_THROW_ERRNO_IF((return_value<0), (pre_str))

#define X_DEBUG_THROW_ERRNO_IF_NEG(return_value, pre_str) \
  X_DEBUG_THROW_ERRNO_IF((return_value<0), (pre_str))


// throws gai_exception with return_value code using gai_strerror()
#define X_THROW_GAI_IF_ER(return_value, pre_str) \
  X_THROW_IF((return_value!=0), gai_exception(return_value,pre_str))

#define X_DEBUG_THROW_GAI_IF_ER(return_value, pre_str) \
  X_DEBUG_THROW_IF((return_value!=0), gai_exception(return_value,pre_str))



#endif	/* _X_EXCEPT_HPP */

